/**
 * Software License, Version 1.0
 * 
 * Copyright 2008 The Trustees of Indiana University.  All rights reserved.
 * 
 *
 *Redistribution and use in source and binary forms, with or without 
 *modification, are permitted provided that the following conditions are met:
 *
 *1) All redistributions of source code must retain the above copyright notice,
 * the list of authors in the original source code, this list of conditions and
 * the disclaimer listed in this license;
 *2) All redistributions in binary form must reproduce the above copyright 
 * notice, this list of conditions and the disclaimer listed in this license in
 * the documentation and/or other materials provided with the distribution;
 *3) Any documentation included with all redistributions must include the 
 * following acknowledgment:
 *
 *"This product includes software developed by the Community Grids Lab. For 
 * further information contact the Community Grids Lab at 
 * http://communitygrids.iu.edu/."
 *
 * Alternatively, this acknowledgment may appear in the software itself, and 
 * wherever such third-party acknowledgments normally appear.
 * 
 *4) The name Indiana University or Community Grids Lab or Granules, 
 * shall not be used to endorse or promote products derived from this software 
 * without prior written permission from Indiana University.  For written 
 * permission, please contact the Advanced Research and Technology Institute 
 * ("ARTI") at 351 West 10th Street, Indianapolis, Indiana 46202.
 *5) Products derived from this software may not be called Granules, 
 * nor may Indiana University or Community Grids Lab or Granules appear
 * in their name, without prior written permission of ARTI.
 * 
 *
 * Indiana University provides no reassurances that the source code provided 
 * does not infringe the patent or any other intellectual property rights of 
 * any other entity.  Indiana University disclaims any liability to any 
 * recipient for claims brought by any other entity based on infringement of 
 * intellectual property rights or otherwise.  
 *
 * LICENSEE UNDERSTANDS THAT SOFTWARE IS PROVIDED "AS IS" FOR WHICH NO 
 * WARRANTIES AS TO CAPABILITIES OR ACCURACY ARE MADE. INDIANA UNIVERSITY GIVES
 * NO WARRANTIES AND MAKES NO REPRESENTATION THAT SOFTWARE IS FREE OF 
 * INFRINGEMENT OF THIRD PARTY PATENT, COPYRIGHT, OR OTHER PROPRIETARY RIGHTS. 
 * INDIANA UNIVERSITY MAKES NO WARRANTIES THAT SOFTWARE IS FREE FROM "BUGS", 
 * "VIRUSES", "TROJAN HORSES", "TRAP DOORS", "WORMS", OR OTHER HARMFUL CODE.  
 * LICENSEE ASSUMES THE ENTIRE RISK AS TO THE PERFORMANCE OF SOFTWARE AND/OR 
 * ASSOCIATED MATERIALS, AND TO THE PERFORMANCE AND VALIDITY OF INFORMATION 
 * GENERATED USING SOFTWARE.
 */
package cgl.granules.exchange;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;

import cgl.granules.dataset.DatasetException;
import cgl.granules.exception.DeploymentException;
import cgl.granules.exception.MarshallingException;
import cgl.granules.operation.Operation;
import cgl.granules.util.MarshallingUtil;

/**
 * This class encapsulates the exchanges related to the deployment of an
 * application instance.
 * 
 * @author Shrideep Pallickara
 * 
 */
public class DeploymentMessage extends Exchange {
    /** These constants correspond to the ExchangeSubType */
    public static final int DEPLOY = 1;
    public static final int STATUS = 2;
    public static final int ABORT = 3;

    private String jobIdentifier;
    private Operation operation;
    private String className;

    private String taskIdentifier;


    /** The null constructor for the DeploymnetMessage */
    protected DeploymentMessage() {
    }


    /**
     * Creates a deployment message based on the specified parameters.
     * 
     * @param jobIdentifier
     *            The ID to be associated with the deployment
     * @param operation
     *            The operation being deployed
     * @param respondTo
     *            The stream over which responses need to be sent.
     * @throws DeploymentException
     *             If problems are encountered with the specified parameters, or
     *             if any of them are NULL.
     */
    protected DeploymentMessage(String jobIdentifier, Operation operation,
            String respondTo) throws DeploymentException {
        this.jobIdentifier = jobIdentifier;
        this.operation = operation;

        className = operation.getClass().getCanonicalName();

        if (jobIdentifier == null) {
            throwDeploymentException("The specified JobIdentifier is NULL");
        }
        if (operation == null) {
            throwDeploymentException("The specified operation is NULL");
        }
        if (respondTo == null) {
            throwDeploymentException("The specified RespondTo stream is NULL");
        }

        setRespondTo(respondTo);
        setExchangeType(ExchangeType.DEPLOYMENT);
        setExchangeSubType(DEPLOY);
        setRequest();
    }


    /**
     * Creates a DeploymentMessage that is a request for either getting the
     * STATUS or ABORTing a previously deployment operation.
     * 
     * @param jobIdentifier
     *            The ID to be associated with the deployment
     * @param taskIdentifier
     *            The task identifier that was assigned to one of the
     *            constituent tasks that comprises the system-wide deployment.
     * @param respondTo
     *            The stream over which responses need to be sent.
     * @param exchangeSubType
     *            This should be either the STATUS or ABORT exchange subTypes.
     * @throws DeploymentException
     *             If problems are encountered with the specified parameters, or
     *             if any of them are NULL or if the exchangeSubType is
     *             something other than DeploymentMessage.STATUS or
     *             DeploymentMessage.ABORT
     */
    protected DeploymentMessage(String jobIdentifier, String taskIdentifier,
            String respondTo, int exchangeSubType) throws DeploymentException {
        this.jobIdentifier = jobIdentifier;
        this.taskIdentifier = taskIdentifier;

        if (jobIdentifier == null) {
            throwDeploymentException("The specified JobIdentifier is NULL");
        }
        if (taskIdentifier == null) {
            throwDeploymentException("The specified TaskIdentifier is NULL");
        }
        if (respondTo == null) {
            throwDeploymentException("The specified RespondTo stream is NULL");
        }

        boolean valid = false;
        if (exchangeSubType == ABORT || exchangeSubType == STATUS) {
            valid = true;
        }
        if (!valid) {
            throwDeploymentException("Invalid exchangeSubType ("
                + exchangeSubType + ") specified ");
        }

        setRespondTo(respondTo);
        setExchangeType(ExchangeType.DEPLOYMENT);
        setExchangeSubType(exchangeSubType);
        setRequest();
        setRespondTo(respondTo);
    }


    /**
     * Creates a deployment response, to a previously issued request. This
     * includes the task identifiers and the exchangeSUbType, which indicates
     * whether this is a response to a previously issued DEPLOY, STATUS or ABORT
     * request. A condition flag is also included in some cases to indicate the
     * status of processing the previously issued request. The description
     * element includes a detailed description of the response.
     * 
     * @param jobIdentifier
     *            The ID to be associated with the deployment
     * @param taskIdentifier
     *            The task identifier that was assigned to one of the
     *            constituent tasks that comprises the system-wide deployment.
     * @param exchangeSubType
     *            Indicates whether this is a response to a previously issued
     *            DEPLOY, STATUS or ABORT
     * @param conditionFlag
     *            Indicates the status of processing the previously issued
     *            request.
     * @param description
     *            A detailed description or reason for the response
     * @throws DeploymentException
     */
    protected DeploymentMessage(String jobIdentifier, String taskIdentifier,
            int exchangeSubType, int conditionFlag, String description)
        throws DeploymentException {
        this.jobIdentifier = jobIdentifier;
        this.taskIdentifier = taskIdentifier;

        if (jobIdentifier == null) {
            throwDeploymentException("The specified JobIdentifier is NULL");
        }
        if (taskIdentifier == null) {
            throwDeploymentException("The specified TaskIdentifier is NULL");
        }

        boolean valid = false;
        if (exchangeSubType == ABORT || exchangeSubType == STATUS
            || exchangeSubType == DEPLOY) {
            valid = true;
        }

        if (!valid) {
            throwDeploymentException("Invalid exchangeSubType ("
                + exchangeSubType + ") specified ");
        }

        setExchangeType(ExchangeType.DEPLOYMENT);
        setExchangeSubType(exchangeSubType);
        setResponse();
        setDescription(description);
        setConditionFlag(conditionFlag);

    }


    /**
     * @return the jobIdentifier
     */
    public String getJobIdentifier() {
        return jobIdentifier;
    }


    /**
     * @return the operation
     */
    public Operation getOperation() {
        return operation;
    }


    /**
     * @return the className
     */
    public String getClassName() {
        return className;
    }


    /**
     * 
     * @return the taskIdentifier
     */
    public String getTaskIdentifier() {
        return taskIdentifier;
    }


    @Override
    public byte[] marshallExchangeSpecificElements() throws IOException,
        MarshallingException {
        byte[] marshalledBytes = null;
        ByteArrayOutputStream baOutputStream = new ByteArrayOutputStream();
        DataOutputStream dout =
            new DataOutputStream(new BufferedOutputStream(baOutputStream));
        MarshallingUtil marshaller = MarshallingUtil.getInstance();

        marshaller.writeString(dout, jobIdentifier);

        boolean hasTaskIdentifier = true;
        if (taskIdentifier == null) {
            hasTaskIdentifier = false;
        }
        dout.writeBoolean(hasTaskIdentifier);
        if (hasTaskIdentifier) {
            marshaller.writeString(dout, taskIdentifier);
        }

        if (getExchangeSubType() == DEPLOY && isRequest()) {
            marshaller.writeString(dout, className);
            byte[] operationBytes = operation.getBytes();
            dout.writeInt(operationBytes.length);
            dout.write(operationBytes);
        }

        dout.flush();
        marshalledBytes = baOutputStream.toByteArray();
        return marshalledBytes;
    }


    @Override
    public void unmarshallExchangeSpecificElements(byte[] exchangeSpecificBytes)
        throws IOException, MarshallingException {
        ByteArrayInputStream baInputStream =
            new ByteArrayInputStream(exchangeSpecificBytes);
        DataInputStream din =
            new DataInputStream(new BufferedInputStream(baInputStream));
        MarshallingUtil marshaller = MarshallingUtil.getInstance();

        jobIdentifier = marshaller.readString(din);
        boolean hasTaskIdentifier = din.readBoolean();
        if (hasTaskIdentifier) {
            taskIdentifier = marshaller.readString(din);
        }

        if (getExchangeSubType() == DEPLOY && isRequest()) {
            className = marshaller.readString(din);
            //System.out.println(className);
            byte[] operationBytes = new byte[din.readInt()];
            din.readFully(operationBytes);
            String errorReport = null;
            try {
                Class c = Class.forName(className);
                operation = (Operation) c.newInstance();
                operation.unmarshall(operationBytes);
            } catch (ClassNotFoundException cfe) {
                errorReport = "Class could not be found " + cfe.toString();
                throwMarshallingException(errorReport);
            } catch (InstantiationException e) {
                errorReport = "Problems instantiating class " + e.toString();
                throwMarshallingException(errorReport);
            } catch (IllegalAccessException e) {
                errorReport =
                    "IllegalAccessException when instantiating class: "
                        + e.toString();
                throwMarshallingException(errorReport);
            } catch (DatasetException e) {
                errorReport =
                    "Problems recreating the dataset for Operation: "
                        + e.toString();
                throwMarshallingException(errorReport);
            }
        }
    }


    private void throwMarshallingException(String reason)
        throws MarshallingException {
        throw new MarshallingException(reason);
    }


    private void throwDeploymentException(String reason)
        throws DeploymentException {
        throw new DeploymentException(reason);
    }


    @Override
    public String toString() {
        String representation = "DeploymentMessage: ";

        int exchangeSubType = getExchangeSubType();
        if (exchangeSubType == DEPLOY) {
            representation += "DEPLOY ";
        } else if (exchangeSubType == STATUS) {
            representation += "STATUS ";
        } else if (exchangeSubType == ABORT) {
            representation += "ABORT ";
        }

        if (isRequest()) {
            representation += "(Request)";
        }

        if (isResponse()) {
            representation += "(Response)";
        }

        representation +=
            (" Job Identifier = " + jobIdentifier + ", Task Identifier = " + taskIdentifier);

        if (getDescription() != null) {
            representation += ("  " + getDescription());
        }
        return representation;
    }


    @Override
    public boolean isValidExchangeSubType() {
        int exchangSubType = getExchangeSubType();
        if (exchangSubType == DEPLOY || exchangSubType == ABORT
            || exchangSubType == STATUS) {
            return true;
        }

        return false;
    }

}
